package com.wedimob.task;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;

public abstract class Task<I, O> extends Observable implements Runnable {

	protected I input;
	protected O output;
	private Throwable e;
	private int progress;
	volatile private Status status;

	private static enum Status {
		NOT_STARTED, WAITING_SEMAPHORE, WAITING_CHILDS, RUNNING, SLEEPING, CANCELING, CANCELED, DONE, ERROR, FATHER_ERROR
	}

	private List<Task<?, I>> fathers = new ArrayList<>();
	private List<Task<O, ?>> childs = new ArrayList<>();

	public Task(I input) {
		this.input = input;
		status = Status.NOT_STARTED;
		e = null;
		output = null;
		progress = 0;
	}

	public int getProgress() {
		return progress;
	}

	public void setProgress(int progress) {
		if (this.progress != progress) {
			this.progress = progress;
			setChanged();
			notifyObservers(progress);
		}
	}

	public I getInput() {
		return input;
	}

	public O getOutput() {
		return output;
	}

	public boolean hasError() {
		return e != null;
	}

	public Throwable getError() {
		return e;
	}

	public Status getStatus() {
		return status;
	}

	public Task<O, ?>[] then(Task<O, ?>... tasks) {
		this.childs.addAll(Arrays.asList(tasks));
		for (Task<O, ?> task : tasks) {
			task._before(this);
		}
		return tasks;
	}

	protected void _before(Task<?, I> father) {
		this.fathers.add(father);
	}

	public void before(Task<?, I> father) {
		father.then(this);
	}

	public Task<?, ?> root() {
		if (fathers.size() == 0)
			return this;

		return fathers.iterator().next().root();
	}

	abstract protected int runcost() throws Throwable;

	public int cost() throws Throwable {
		int cost = runcost();
		int subcost = 0;
		for (Task<O, ?> task : childs) {
			int subcostJ = task.runcost();
			if (subcostJ > subcost)
				subcost = subcostJ;
		}
		return cost + subcost;
	}

	@Override
	public void run() {
		try {
			for (Task<?, I> task : fathers) {
			}

			status = Status.RUNNING;
			runtask();
			status = Status.WAITING_CHILDS;
			Map<Task<O, ?>, Thread> threads = new HashMap<>();
			for (Task<O, ?> task : childs) {
				Thread thread = new Thread(task);
				threads.put(task, thread);
				thread.start();
			}

			for (Task<O, ?> task : childs) {
				threads.get(task).join();
			}

			status = Status.DONE;
		} catch (Throwable e) {
			e.printStackTrace();
			this.e = e;
			status = Status.ERROR;
		}
	}

	abstract protected void runtask() throws Throwable;
}
